<!doctype html><html lang=zh-cn><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Rust 模块和文件</title><link rel=stylesheet href=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/notes.css media=all><script src=/static/main.js></script><body data-category=default data-clipid=1567482283><div class="mx-wc-main yue"><div><div><div><article><p>原文链接：<a href=https://amos.me/blog/2019/rust-modules-vs-files/>amos.me - Rust modules vs files</a><hr><p>不久前，我在推特上发起了 <a href=https://twitter.com/fasterthanlime/status/1142183262779052051>Rust 有什么让人困惑的</a>
话题，热度最高的主题是“模块系统是怎么映射到文件的？”。<p>我记得刚接触 Rust 时模块让我痛苦挣扎，所以我尝试用一种我认为说得通的方式解释它。<h2>要点</h2><p>以下所述均使用 Rust 2018 版本。我没有兴趣学习（或教授）老版本的细节，特别是因为老版本让我更加困惑。<p>如果你有现存的项目，你可以查看 <code class=highlighter-rouge>Cargo.toml</code> 文件中的 <code class=highlighter-rouge>edtion</code> 查看项目使用的 Rust 版本。
如果没有，那现在就加上 <code class=highlighter-rouge>edition = 2018</code>。<p>如果使用最新的 Rust 且通过 <code class=highlighter-rouge>cargo new/ cargo init</code> 来创建新项目，新项目会自动选择 2018 版本。<h2>什么是 crate</h2><p>一个 crate 通常来说是一个项目。它有一个 <code class=highlighter-rouge>Cargo.toml</code> 文件，这个文件用于声明依赖，入口，构建选项等项目元数据。
每个 crate 可以独立地在 <a href=https://crates.io/>https://crates.io/</a> 上发表。<p>假设我们要创建一个二进制（可执行）项目：<ul><li><code class=highlighter-rouge>cargo new --bin</code>（或者在已有项目上用 <code class=highlighter-rouge>cargo init --bin</code>）会为新 crate 生成一个 <code class=highlighter-rouge>Cargo.toml</code> 文件。<li>项目入口为 <code class=highlighter-rouge>src/main.rs</code></ul><p>对于二进制项目，<code class=highlighter-rouge>src/main.rs</code> 是项目主模块的常用路径。它不一定是精确的路径,可以在 <code class=highlighter-rouge>Cargo.toml</code> 添加相应配置 <sup><a href=#fn:1>1</a></sup>，使编译器在别处查看(甚至可以有多个目标二进制文件和多个目标库)。<p>默认情况下，我们的可执行项目的 <code class=highlighter-rouge>src/main.rs</code> 如下：<div><div><pre class=highlight><code class="hljs rust">fn main() {
    println!(&#34;Hello world!&#34;);
}
</code></pre></div></div><p>我们可以通过 <code class=highlighter-rouge>cargo run</code> 构建和运行这个项目，若只想构建项目，则运行 <code class=highlighter-rouge>cargo build</code><p>构建一个 crate 的时候，cargo 下载并编译所有所需依赖，默认情况下把临时文件和最终生成文件放入 <code class=highlighter-rouge>./target/</code> 目录下。
cargo 既是包管理器又是构建系统。<h2>crate 依赖</h2><p>让我们向刚才创建的 crate 添加 <code class=highlighter-rouge>rand</code> 依赖来看看命名空间是怎么工作的。我们需要修改 <code class=highlighter-rouge>Cargo.toml</code>，其内容如下：<div><div><pre class=highlight><code class="hljs ini">[package]
name = &#34;modules&#34;
version = &#34;0.1.0&#34;
edition = &#34;2018&#34;

[dependencies]
rand = &#34;0.7.0&#34;
</code></pre></div></div><p>如果我们想学习如何使用 <code class=highlighter-rouge>rand</code> crate，有以下几种方式：<ul><li>rand 的 <a href=https://crates.io/crates/rand>crates.io.page</a> - 上面通常包含了一个类似 README 文件，包含了简要描述和一些代码示例<li>rand 的 <a href=https://rust-random.github.io/rand/rand/index.html>文档</a>（在 crates.io 页面标题或最新版本下有链接）。需要注意的是所有发表在 crates.io 的 crate 会在 <a href=https://docs.rs/>https://docs.rs</a> 上生成文件 - 我不确定为什么 rand 也文档部署在它自己的网页，或许它早于 docs.rs？<li>它的 <a href=https://github.com/rust-random/rand>源码页</a>，如果其他方式（如 crates.io 的链接和自动生成的文档）失败了的化</ul><p>现在让我们在 <code class=highlighter-rouge>src/main.rs</code> 里使用 <code class=highlighter-rouge>rand</code>, <code class=highlighter-rouge>src/main.rs</code> 如下：<div><div><pre class=highlight><code class="hljs rust">fn main() {
    let random_boolean = rand::random();
    println!(&#34;You {}!&#34;, if random_boolean { &#34;win&#34; } else { &#34;lose&#34; });
}
</code></pre></div></div><p>请注意：<ul><li>我们不需要使用 <code class=highlighter-rouge>use</code> 指令来使用 <code class=highlighter-rouge>rand</code> - 它在项目下的文件全局可用，因为它在 <code class=highlighter-rouge>Cargo.toml</code> 中被声明为依赖（rust 2018之前的版本则不是这样）<li>我们完全没必要使用 <code class=highlighter-rouge>mod</code> （稍后讲述）</ul><p>为了明白这篇博客的余下部分，你需要明白 rust 模块仅仅是命名空间 - 他们让你把相关符号组合在一起并保证可见性规则。<ul><li>我们的 crate 有一个主模块（我们现在所在），它的源在 <code class=highlighter-rouge>src/main.rs</code><li><code class=highlighter-rouge>rand</code> crate 也有一个入口。因为他是一个库，默认情况下其主入口为 <code class=highlighter-rouge>src/lib.rs</code><li>在我们主模块范围，我们可以在主模块通过依赖名称使用依赖</ul><p>总之，我们现在只处理两个模块：我们项目主入口还有 <code class=highlighter-rouge>rand</code> 的入口。<h2>use 指令</h2><p>如果我们不喜欢一直这样写 <code class=highlighter-rouge>rand::random()</code>，我们可以把 <code class=highlighter-rouge>random</code> 注入主模块范围。<div><div><pre class=highlight><code class="hljs rust">use rand::random;
// 我们可以通过 `rand::random()` 或 `random()` 来使用它

fn main() {
    if random() &amp;&amp; random() {
        println!(&#34;You won twice in a row!&#34;);
    } else {
        println!(&#34;Try again...&#34;);
    }
}
</code></pre></div></div><p>我们也可以使用通配符来导入 <code class=highlighter-rouge>rand</code> 主模块导出的所有符号。<div><div><pre class=highlight><code class="hljs rust">// 这会导入 random，还有 thead_rng 等
use rand::*;

fn main() {
    if random() {
        panic!(&#34;Unlucky coin toss&#34;);
    }
    println!(&#34;Hello world&#34;);
}
</code></pre></div></div><h2>模块不需要在分开的文件里</h2><p>正如刚才所见，模块是一个让你组合相关符号的语言结构。<p>你<em>不需要</em>把他们放在不同的文件下。<p>让我们修改下 <code class=highlighter-rouge>src/main.rs</code> 来证明这个观点：<div><div><pre class=highlight><code class="hljs rust">mod math {
    pub fn add(x: i32, y: i32) -&gt; i32 {
        x + y
    }
    // 使用 `pub` 来导出 `add()` 函数
    // 如果不这样做，`add()` 会变为 `math` 模块的私有函数
    // 我们将无法在 `math` 模块外使用它
}

fn main() {
    let result = math::add(1, 2);
    println!(&#34;1 + 2 = {}&#34;, result);
}
</code></pre></div></div><p>从范围角度，我们项目结构如下：<div><div><pre class=highlight><code class="hljs javascript">我们 crate 的主模块
    `math`: 我们的 `math` 模块
    `rand`: `rand` crate 的主模块
</code></pre></div></div><p>从文件角度，主模块和 <code class=highlighter-rouge>math</code> 模块都在同一个文件 <code class=highlighter-rouge>src/main.rs</code> 下。<h2>模块可以在可分开的文件中</h2><p>现在，如果我们如下修改项目：<p><code class=highlighter-rouge>src/math.rs</code><div><div><pre class=highlight><code class="hljs rust">pub fn add(x: i32, y: i32) -&gt; i32 {
    x + y
}
</code></pre></div></div><p><code class=highlighter-rouge>src/main.rs</code><div><div><pre class=highlight><code class="hljs rust">fn main() {
    let result = math::add(1, 2);
    println!(&#34;1 + 2 = {}&#34;, result);
}
</code></pre></div></div><p>然而这行不通。<div><div><pre class=highlight><code class="hljs sql">Compiling modules v0.1.0 (/home/amos/Dev/modules)
error[E0433]: failed to resolve: use of undeclared type or module `math`
 --&gt; src/main.rs:2:18
  |
2 |     let result = math::add(1, 2);
  |                  ^^^^ use of undeclared type or module `math`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0433`.
error: Could not compile `modules`.

To learn more, run the command again with --verbose.
</code></pre></div></div><p>虽然 <code class=highlighter-rouge>src/main.rs</code> 和 <code class=highlighter-rouge>src/lib.rs</code>（二进制和库项目）会被 cargo 自动识别为程序入口，其他文件则需要在文件中明确声明。<p>我们的错误在于仅仅创建了 <code class=highlighter-rouge>src/math.rs</code> 文件，希望 cargo 会在构建时找到它，但事实上并不是这样的。cargo 甚至不会解析它。
<code class=highlighter-rouge>cargo check</code> 命令也不会报错，因为 <code class=highlighter-rouge>src/math.rs</code> 现在还不是 crate 源文件的一部分。<p>为了改正这个错误，可以如下修改 <code class=highlighter-rouge>src/main.rs</code>（因为它时项目入口，这是 cargo 已知的）:<div><div><pre class=highlight><code class="hljs rust">mod math {
    include!(&#34;math.rs&#34;);
}
// 注意: 这不是符合 rust 风格的写法，仅作 mod 学习用

fn main() {
    let result = math::add(1, 2);
    println!(&#34;1 + 2 = {}&#34;, result);
}
</code></pre></div></div><p>现在 crate 可以编译和运行了，因为：<ul><li>我们定义了一个名为 <code class=highlighter-rouge>math</code> 的模块<li>我们告诉编译器复制/粘贴其他文件（<code class=highlighter-rouge>math.rs</code>）到模块代码块中<ul><li>参考 <a href=https://doc.rust-lang.org/stable/std/macro.include.html>include! 文档</a></ul></ul><p>但这不是通常导入模块的方式。按照惯例，如果使用不跟随代码块的 <code class=highlighter-rouge>mod</code> 指令，效果上述一样。<p>所以也可以这样写：<div><div><pre class=highlight><code class="hljs rust">mod math;

fn main() {
    let result = math::add(1, 2);
    println!(&#34;1 + 2 = {}&#34;, result);
}
</code></pre></div></div><p>就是这么简单。但容易混淆之处在于，根据 <code class=highlighter-rouge>mod</code> 之后是否有代码块，它可以内联定义模块，或者导入其他文件。<p>这也解释了为什么在 <code class=highlighter-rouge>src/math.rs</code> 里不用再定义另一个 <code class=highlighter-rouge>mod math {}</code>。因为 <code class=highlighter-rouge>src/math.rs</code> 已经在
<code class=highlighter-rouge>src/main.rs</code> 中导入，它已经说 <code class=highlighter-rouge>src/math.rs</code> 的代码存在于一个名为 <code class=highlighter-rouge>math</code> 的模块中。<h2>那 <code class=highlighter-rouge>use</code> 呢</h2><p>现在我们几乎了解了 <code class=highlighter-rouge>mod</code>，那 <code class=highlighter-rouge>use</code> 呢？<p><code class=highlighter-rouge>use</code> 的唯一目的是<em>将符号带入命名空间</em>，让符号使用更加简短。<p>特别是，<code class=highlighter-rouge>use</code> <strong>永远不会告诉编译器去编译 mod 导入文件之外的其他文件</strong>。<p>在 <code class=highlighter-rouge>main.rs</code>/<code class=highlighter-rouge>math.rs</code> 例子中，在 <code class=highlighter-rouge>src/main.rs</code> 写下如下语句时：<div><div><pre class=highlight><code class="hljs lua">mod math;
</code></pre></div></div><p>我们在主模块导入一个名为 <code class=highlighter-rouge>math</code> 模块，这个模块导出 <code class=highlighter-rouge>add</code> 函数。<p>从范围角度，结构如下：<div><div><pre class=highlight><code class="hljs javascript">crate 主模块(我们在这儿)
  `math` 模块
    `add` 函数
</code></pre></div></div><p>这就是为什么我们要使用 <code class=highlighter-rouge>add</code> 函数时要这样引用 <code class=highlighter-rouge>math::add</code>，即从主模块到 <code class=highlighter-rouge>add</code> 函数的正确路径。<p>请注意，如果我们从另一个模块调用 <code class=highlighter-rouge>add</code>，那么 <code class=highlighter-rouge>math::add</code> 可能不是有效路径。
然而，<code class=highlighter-rouge>add</code> 有一个更长的添加路径，即 <code class=highlighter-rouge>crate::math::add</code> - 它在我们的 crate 中的任何位置都有效（只要 <code class=highlighter-rouge>math</code> 模块保持原样）。<p>所以，如果我们不想每次都使用 <code class=highlighter-rouge>math::</code> 前缀调用 <code class=highlighter-rouge>add</code>，可以用 <code class=highlighter-rouge>use</code> 指令：<div><div><pre class=highlight><code class="hljs rust">mod math;
use math::add;

fn main() {
    // 看，没有前缀了！
    let result = add(1, 2);
    println!(&#34;1 + 2 = {}&#34;, result);
}
</code></pre></div></div><h2>那 <code class=highlighter-rouge>mod.rs</code> 又是什么呢？</h2><p>好吧，我说谎了 - 我们还没完全了解 <code class=highlighter-rouge>mod</code>。<p>目前，crate 有一个漂亮又扁平的文件结构：<div><div><pre class=highlight><code class="hljs lua">src/
    main.rs
    math.rs
</code></pre></div></div><p>这是有道理的，因为 <code class=highlighter-rouge>math</code> 是一个小模块（只有一个函数），它并不需要拥有自己的文件夹。但我们也可以这样改变它的结构：<div><div><pre class=highlight><code class="hljs lua">src/
    main.rs
    math/
        mod.rs
</code></pre></div></div><p>（对于那些熟悉 node.js 的人来说，<code class=highlighter-rouge>mod.rs</code> 类似于 <code class=highlighter-rouge>index.js</code>）。<p>就命名空间/范围而言，两种结构都是等价的。我们的新 <code class=highlighter-rouge>src/math/mod.rs</code> 与<code class=highlighter-rouge>src/math.rs</code>具有完全相同的内容，
并且我们的 <code class=highlighter-rouge>src/main.rs</code> 完全不变。<p>事实上，如果如果我们定义了 <code class=highlighter-rouge>math</code> 模块的子模块， <code class=highlighter-rouge>folder/mod.rs</code> 结构更加易于理解。<p>假设我们想添加一个 <code class=highlighter-rouge>sub</code> 函数，因为我们强制执行“一个函数一个文件”的限制，我们希望 <code class=highlighter-rouge>add</code> 和 <code class=highlighter-rouge>sub</code> 存在于各自的模块中。<p>我们现在的文件结构如下：<div><div><pre class=highlight><code class="hljs lua">src/
    main.rs
    math/
        mod.rs
        add.rs (新文件!)
        sub.rs (也是新文件!)
</code></pre></div></div><p>概念上而言，命名空间树如下：<div><div><pre class=highlight><code class="hljs lua">crate (src/main.rs)
    `math` 模块 (src/math/mod.rs)
        `add` 模块 (src/math/add.rs)
        `sub` 模块 (src/math/sub.rs)
</code></pre></div></div><p>我们的 <code class=highlighter-rouge>src/main.rs</code> 不需要做很大改动 - <code class=highlighter-rouge>math</code> 仍在相同位置。我们只是让它使用 <code class=highlighter-rouge>add</code> 和 <code class=highlighter-rouge>sub</code>：<div><div><pre class=highlight><code class="hljs lua">// 保证 math 在 `./math.rs` 或 `./math/mod.rs` 中定义
mod math;

// 将两个符号带入范围，在 `math` 模块中保证都已导出
use math::{add, sub};

fn main() {
    let result = add(1, 2);
    println!(&#34;1 + 2 = {}&#34;, result);
}
</code></pre></div></div><p>我们的 <code class=highlighter-rouge>src/math/add.rs</code> 正如我们在 <code class=highlighter-rouge>math</code> 模块做的一样：定义一个函数，并用 <code class=highlighter-rouge>pub</code> 将其导出。<div><div><pre class=highlight><code class="hljs rust">pub fn add(x: i32, y: i32) -&gt; i32 {
    x + y
}
</code></pre></div></div><p>类似地，<code class=highlighter-rouge>src/math/sub.rs</code> 文件如下：<div><div><pre class=highlight><code class="hljs rust">pub fn sub(x: i32, y: i32) -&gt; i32 {
    x - y
}
</code></pre></div></div><p>现在来看 <code class=highlighter-rouge>src/math/mod.rs</code>。我们知道 cargo 知道 <code class=highlighter-rouge>math</code> 这个模块存在，
因为 <code class=highlighter-rouge>src/main.rs</code> 中的 <code class=highlighter-rouge>mod math;</code> 语句已将其导入。
但我们需要让 cargo 也知道 <code class=highlighter-rouge>add</code> 和 <code class=highlighter-rouge>sub</code> 模块。<p>所以我们需要在 <code class=highlighter-rouge>src/math/mod.rs</code> 添加如下语句；<div><div><pre class=highlight><code class="hljs lua">mod add;
mod sub;
</code></pre></div></div><p>现在 cargo 知晓所有源文件。<p>crate 能编译成功吗？（剧透一下：没有哦）<div><div><pre class=highlight><code class="hljs lua">   Compiling modules v0.1.0 (/home/amos/Dev/modules)
error[E0603]: module `add` is private
 --&gt; src/main.rs:2:12
  |
2 | use math::{add, sub};
  |            ^^^

error[E0603]: module `sub` is private
 --&gt; src/main.rs:2:17
  |
2 | use math::{add, sub};
  |                 ^^^
</code></pre></div></div><p>发生了什么？好吧，按现在的写法，主模块看起来是这样的：<div><div><pre class=highlight><code class="hljs lua">crate (我们在这儿)
    `math` 模块
        （空的）
</code></pre></div></div><p>所以 <code class=highlighter-rouge>math::add</code> 不是一个有效路径，因为 <code class=highlighter-rouge>math</code> 模块没有导出任何东西。<p>好吧，我猜我们可以直接在 <code class=highlighter-rouge>mod</code> 前加上 <code class=highlighter-rouge>pub</code>？<p>将 <code class=highlighter-rouge>src/math/mod.rs</code> 做如下修改：<div><div><pre class=highlight><code class="hljs rust">pub mod add;
pub mod sub;
</code></pre></div></div><p>又一次，编译不通过：<div><div><pre class=highlight><code class="hljs sql">   Compiling modules v0.1.0 (/home/amos/Dev/modules)
error[E0423]: expected function, found module `add`
 --&gt; src/main.rs:5:18
  |
5 |     let result = add(1, 2);
  |                  ^^^ not a function
help: possible better candidate is found in another module, you can import it into scope
  |
2 | use crate::math::add::add;
  |
</code></pre></div></div><p>rustc 给出了明确的信息 - 现在我们公开了 <code class=highlighter-rouge>add</code> 和 <code class=highlighter-rouge>sub</code> 模块，我们的 crate 模块结构如下：<div><div><pre class=highlight><code class="hljs javascript">crate (我们在这)
    `math` 模块
        `add` 模块
            `add` 函数
        `sub` 模块
            `sub` 函数
</code></pre></div></div><p>但这和期望略有差距。<code class=highlighter-rouge>math</code> 的两个子模块组成涉及实现细节。我们并不希望导出这两个模块 - 我们也不希望任何人直接导入这两个模块！<p>所以回到声明和导入子模块的地方，让这两个模块变为私有，然后分别重新导出它们的 <code class=highlighter-rouge>add</code> 和 <code class=highlighter-rouge>sub</code> 函数。<div><div><pre class=highlight><code class="hljs rust">// 子模块是私有的
mod add;
mod sub;

// 这些是重导出函数
pub use add::add;
pub use sub::sub;
</code></pre></div></div><p>这样改变后，从 <code class=highlighter-rouge>src/math/mod.rs</code> 角度看，模块结构如下：<div><div><pre class=highlight><code class="hljs javascript">`math` 模块（我们在这）
    `add` 函数（公开）
    `sub` 函数（公开）
    `add` 模块（私有）
        `add` 函数（公开）
    `sub` 模块（私有）
        `sub` 函数（公开）
</code></pre></div></div><p>然而，从 <code class=highlighter-rouge>src/main.rs</code> 角度看，模块结构如下：<div><div><pre class=highlight><code class="hljs javascript">crate （你在这）
    `math` 模块
        `add` 模块
        `sub` 模块
</code></pre></div></div><p>我们已经成功隐藏 <code class=highlighter-rouge>math</code> 模块的实现细节 - 只有 <code class=highlighter-rouge>add</code> 和 <code class=highlighter-rouge>sub</code> 函数被导出。<p>果然，现在 crate 编译成功且运行良好。<h2>回顾</h2><p>回顾一下，这是目前完整的文件。<p><code class=highlighter-rouge>src/main.rs</code><div><div><pre class=highlight><code class="hljs rust">mod math;
use math::{add, sub};

fn main() {
    let result = add(1, 2);
    println!(&#34;1 + 2 = {}&#34;, result);
}
</code></pre></div></div><p><code class=highlighter-rouge>src/math/mod.rs</code><div><div><pre class=highlight><code class="hljs rust">mod add;
mod sub;

pub use add::add;
pub use sub::sub;
</code></pre></div></div><p><code class=highlighter-rouge>src/math/add.rs</code><div><div><pre class=highlight><code class="hljs rust">pub fn add(x: i32, y: i32) -&gt; i32 {
    x + y
}
</code></pre></div></div><p><code class=highlighter-rouge>src/math/sub.rs</code><div><div><pre class=highlight><code class="hljs rust">pub fn sub(x: i32, y: i32) -&gt; i32 {
    x - y
}
</code></pre></div></div><h2>未使用的导入和符号</h2><p>如果你用编辑器跟随写到现在，你会注意到 rustc（rust 编译器，由 cargo 调用）抛出一个 warning:<div><div><pre class=highlight><code class="hljs sql">warning: unused import: `sub`
 --&gt; src/main.rs:2:17
  |
2 | use math::{add, sub};
  |                 ^^^
  |
  = note: #[warn(unused_imports)] on by default
</code></pre></div></div><p>的确，现在我们没有在主函数使用 <code class=highlighter-rouge>sub</code>。如果我们像下面那样在 <code class=highlighter-rouge>use</code> 指令中把它去掉会怎样？<div><div><pre class=highlight><code class="hljs rust">mod math;
use math::add;

fn main() {
    let result = add(1, 2);
    println!(&#34;1 + 2 = {}&#34;, result);
}
</code></pre></div></div><p>现在 rust 又抛出了错误：<div><div><pre class=highlight><code class="hljs rust">warning: function is never used: `sub`
 --&gt; src/math/sub.rs:1:1
  |
1 | pub fn sub(x: i32, y: i32) -&gt; i32 {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: #[warn(dead_code)] on by default
</code></pre></div></div><p>解释非常简单。目前在 crate 中，<code class=highlighter-rouge>sub</code> 没有在其他地方导出。它在 <code class=highlighter-rouge>src/math/sub.rs</code> 中定义，
由 <code class=highlighter-rouge>src/math/mod.rs</code> 重新导出。<code class=highlighter-rouge>math</code> 模块在且仅在 <code class=highlighter-rouge>src/main.rs</code> 可用 - 但我们没有在主模块中使用它。<p>所以我们让编译器去解析一个源文件，进行类型检查和所有权检查 - 但 <code class=highlighter-rouge>sub</code> 函数在最后的可执行文件并没有出现。即使我们想把
crate 作为一个库，<code class=highlighter-rouge>sub</code> 函数依然不可用，因为它并没有在程序入口导出。<p>我们有几个选项。如果想让 crate 既是一个可执行项目和库，仅需让 <code class=highlighter-rouge>math</code> 模块变为公开就可以了。<p>在 <code class=highlighter-rouge>src/lib.rs</code> 里：<div><div><pre class=highlight><code class="hljs rust">// 现在不必使用 `math` 模块里的所有符号，
// 因为我们让他们对所有依赖可见。
pub mod math;
</code></pre></div></div><p>或者，我们可以去掉 <code class=highlighter-rouge>sub</code> 函数（毕竟我们没有它）。如果我们知道之后将会使用它，可以对某个函数关闭 <code class=highlighter-rouge>warning</code>：<p>在 <code class=highlighter-rouge>src/math/sub.rs</code> 中：<div><div><pre class=highlight><code class="hljs rust">// 这不是好主意
#[allow(unused)]
pub fn sub(x: i32, y: i32) -&gt; i32 {
    x - y
}
</code></pre></div></div><p>但我真的推荐这样做。一旦添加这个注解很容易忘掉死代码。记住，寻找 <code class=highlighter-rouge>unused</code> 是很难的。
这是源码控制该干的。但如果你想要，它仍是一个选择。<p>但这确实回答了一个你可能一直在问自己的问题：“仅仅 <code class=highlighter-rouge>use</code> 我真正需要的东西是不是更好，所以剩下的不会被编译/包含在最终的二进制文件中吗？”。 答案是：没关系。<p>使用通配符导入符号（如 <code class=highlighter-rouge>use::some_crate::*;</code>）的唯一害处是污染命名空间。但编译器还是会解析所有源文件，把没有使用的部分去掉（通过消灭死代码），不管命名空间有什么。<h2>父模块</h2><p>目前我们仅使用了那些命名空间/符号树深处的符号。<p>但如果需要，我们也可以使用父级命名空间里。<p>假设我们希望 <code class=highlighter-rouge>math</code> 模块有一个模块级的常量来开启或关闭日志。<p>（注意，这样控制日志是一个糟糕的做法，我只是暂时想不到其他愚蠢的例子）。<p>现在将 <code class=highlighter-rouge>src/math/mod.rs</code> 做如下修改：<div><div><pre class=highlight><code class="hljs rust">mod add;
mod sub;

pub use add::add;
pub use sub::sub;

const DEBUG: bool = true;
</code></pre></div></div><p>然后我们可以在其他模块引用 <code class=highlighter-rouge>DEBUG</code>，比如 <code class=highlighter-rouge>src/math/add.rs</code>：<div><div><pre class=highlight><code class="hljs rust">pub fn add(x: i32, y: i32) -&gt; i32 {
    if super::DEBUG {
        println!(&#34;add({}, {})&#34;, x, y);
    }
    x + y
}
</code></pre></div></div><p>意料之中，编译通过且成功运行：<div><div><pre class=highlight><code class="hljs ruby">$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/modules`
add(1, 2)
1 + 2 = 3
</code></pre></div></div><p>注意：一个模块总是可以访问其父级作用域（通过 <code class=highlighter-rouge>super::</code>）- 即便是是父级作用域的私有变量、私有函数等。
<code class=highlighter-rouge>DEBUG</code> 是私有的，但我们可以在 <code class=highlighter-rouge>add</code> 模块中使用它。<p>如果我们要定义rust关键字和文件路径惯用语之间的对应关系，我们可以映射：<ul><li><code class=highlighter-rouge>crate::foo</code> 对 <code class=highlighter-rouge>/foo</code> - 如果我们认为“根文件系统”为包含 <code class=highlighter-rouge>main.rs</code> 或 <code class=highlighter-rouge>lib.rs</code> 的目录<li><code class=highlighter-rouge>super::foo</code> 对 <code class=highlighter-rouge>../foo</code><li><code class=highlighter-rouge>self::foo</code> 对 <code class=highlighter-rouge>./foo</code></ul><p>什么时候会需要使用 <code class=highlighter-rouge>self</code> 呢？<p>好吧，对于 <code class=highlighter-rouge>src/math/mod.rs</code> 如下两行：<div><div><pre class=highlight><code class="hljs sql">pub use add::add;
pub use sub::sub;
</code></pre></div></div><p>我们可以用单行代码实现：<div><div><pre class=highlight><code class="hljs sql">pub use self::{add:add, sub::sub};
</code></pre></div></div><p>假设子模块只导出了我们希望使用的符号，我们甚至可以使用通配符：<div><div><pre class=highlight><code class="hljs rust">pub use self::{add::*, sub::*};
</code></pre></div></div><h2>同级模块</h2><p>好吧，同级模块（如 <code class=highlighter-rouge>add</code> 和 <code class=highlighter-rouge>sub</code>）之间没有直接访问的路径。<p>如果想在 <code class=highlighter-rouge>add</code> 中重新定义 <code class=highlighter-rouge>sub</code>，我们在 <code class=highlighter-rouge>src/math/sub.rs</code> 不能这样做：<div><div><pre class=highlight><code class="hljs rust">// 编译不通过
pub fn sub(x: i32, y: i32) -&gt; i32 {
    add::add(x, -y)
}
</code></pre></div></div><p><code class=highlighter-rouge>add</code> 和 <code class=highlighter-rouge>sub</code> 共享父级模块，但不意味他们共享命名空间。<p>我们也绝对不应该使用第二个 <code class=highlighter-rouge>mod</code>。 <code class=highlighter-rouge>add</code> 模块已存在于模块层次结构中的某个位置。
除此之外 - 因为它是 <code class=highlighter-rouge>sub</code> 的子模块，它要么存在于 <code class=highlighter-rouge>src/math/sub/add.rs</code> 或 <code class=highlighter-rouge>src/math/sub/add/mod.rs</code>
中 - 这两者都没有意义。<p>如果我们想访问 <code class=highlighter-rouge>add</code>， 必须通过父级模块，就像其他人一样。在 <code class=highlighter-rouge>src/math/sub.rs</code> 中：<div><div><pre class=highlight><code class="hljs rust">pub fn sub(x: i32, y: i32) -&gt; i32 {
    super::add::add(x, -y)
}
</code></pre></div></div><p>或者使用 <code class=highlighter-rouge>src/math/mod.rs</code> 重新导出的 <code class=highlighter-rouge>add</code>：<div><div><pre class=highlight><code class="hljs rust">pub fn sub(x: i32, y: i32) -&gt; i32 {
    super::add(x, -y)
}
</code></pre></div></div><p>或者简单地导入 <code class=highlighter-rouge>add</code> 模块下的所有东西：<div><div><pre class=highlight><code class="hljs rust">pub fn sub(x: i32, y: i32) -&gt; i32 {
    use super::add::*;
    add(x, -y)
}
</code></pre></div></div><p>请注意，函数有它自己的作用域，所以 <code class=highlighter-rouge>use</code> 不会影响这个模块其他地方。<p>你甚至可以用 {} 限制作用域！<div><div><pre class=highlight><code class="hljs rust">pub fn sub(x: i32, y: i32) -&gt; i32 {
    let add = &#34;something else&#34;;
    let res = {
        // 在这个代码块中，`add` 是 `add` 模块导出的函数
        use super::add::*;
        add(x, -y)
    };
    // 现在我们离开代码块，`add` 又变为 &#34;something else&#34;
    res
}
</code></pre></div></div><h2><code class=highlighter-rouge>preclude</code> 模式</h2><p>随着 crate 变得复杂，模块层次也更复杂。除了从 crate 入口导出所有东西，
一些 crate 选择一下最常用的符号并在 <code class=highlighter-rouge>prelude</code> 中导出他们。<p><a href=https://crates.io/crates/chrono>chrono</a> 就是一个好例子。<p>查看它在 <a href=https://docs.rs/>https://docs.rs</a> 上的文档，它的主入口导出如下东西：<p><a href=https://postimg.cc/dhX7qX0k><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/82f02865.png data-url=https://i.postimg.cc/Ls4jVFKT/chrono-exports.png data-uploaded=true></a><p>所以如果这样写：<div><div><pre class=highlight><code class="hljs sql">use chrono::*;
</code></pre></div></div><p>将会在作用域内导入 <code class=highlighter-rouge>serde</code>，这会遮盖 <code class=highlighter-rouge>serde</code> crate。<p>这也是为什么 chrono 使用 <code class=highlighter-rouge>preclude</code> 模块，这个模块只导出如下内容：<p><a href=https://postimg.cc/6Tw85CB0><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/676ccc0a.png data-url=https://i.postimg.cc/7PJ09Ncp/chrono-prelude-exports.png data-uploaded=true></a><h2>结论</h2><p>我希望这些能澄清 rust 的模块和文件，如果有任何疑问，请在 <a href=https://twitter.com/fasterthanlime>Twitter</a>上告诉我。感谢阅读！<hr><p>Github 博客地址：<a href=https://privaterookie.github.io/2019-07-14-Rust%E6%A8%A1%E5%9D%97%E4%B8%8E%E6%96%87%E4%BB%B6/>Rust 模块与文件</a><p>知乎专栏：<a href=https://zhuanlan.zhihu.com/p/73544030>夜雨秋灯录 - Rust模块与文件</a><p>LearnKu 社区文章: <a href=https://learnku.com/articles/31161>Rust 模块与文件</a><div><ol><li><p>具体配置参考 <a href=https://rustlang-cn.org/office/rust/cargo/>Cargo教程</a> <a href=#fnref:1>↩</a></ol></div></article></div></div></div><hr><div><label>原网址: <a href=https://privaterookie.github.io/2019-07-14-Rust%E6%A8%A1%E5%9D%97%E4%B8%8E%E6%96%87%E4%BB%B6/>访问</a></label><br><label>创建于: 2019-09-03 11:44:43</label><br><label>目录: default</label><br><label>标签: <code>rust</code></label></div></div><script src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/highlight.pack.js></script><link rel=stylesheet href=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/highlight.vs.css><script>
        hljs.initHighlightingOnLoad();
        document.querySelectorAll('pre.hljs').forEach((block) => {
            hljs.highlightBlock(block);
        });
    </script>